package pres.lnk.learn.thread;

import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * 主线程等待子线程完成
 *
 * @Author lnk
 * @Date 2018/1/30
 */
public class ThreadMainWaitChildren {

    /**
     * <p>方式一</p>
     * 使用线程池
     */
    @Test
    public void test1() throws InterruptedException {
        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            int finalI = i;
            list.add(new Thread() {
                @Override
                public void run() {
                    System.out.println("子线程 " + finalI + " 开始执行");
                    try {
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("子线程 " + finalI + " 执行线束");
                }
            });
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        ExecutorService fixedPool = Executors.newFixedThreadPool(list.size());

        System.out.println("主线程开始执行");
        for (Thread t : list) {
            fixedPool.execute(t);
        }
        // 线程池用完就要关闭掉，不然就算主线程停了资源也不会释放。
        // 调用shutdown()方法不会马上关闭的线程池，会等所有子线程跑完了才会关闭，但这时不能再添加线程了。
        fixedPool.shutdown();

        // 等待所有子线程执行完，为了避免子线程卡死（如死锁等），需要设置超时时间
        fixedPool.awaitTermination(10, TimeUnit.SECONDS);
        System.out.println("主线程执行结束");
    }

    /**
     * <p>方式二</p>
     * 使用计数器
     */
    @Test
    public void test2() throws InterruptedException {
        // 使用计数器的话，事先要知道有多少个线程
        CountDownLatch count = new CountDownLatch(3);

        System.out.println("主线程开始执行");
        new ThreadCountDown("线程一", count).start();
        new ThreadCountDown("线程二", count).start();
        new ThreadCountDown("线程三", count).start();

        // 等待全部子线程完成
        count.await();
        // 可设置线程等待超时，推荐使用
        count.await(10, TimeUnit.SECONDS);

        System.out.println("主线程执行结束");
    }

    /**
     * 计数器子线程
     */
    public static class ThreadCountDown extends Thread {
        private String name;
        private CountDownLatch count;

        public ThreadCountDown(String name, CountDownLatch count) {
            this.name = name;
            this.count = count;
        }

        @Override
        public void run() {
            System.out.println(name + " 开始执行");
            try {
                sleep(1000);
                System.out.println(name + " 执行线束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 使用计数器必需要用try捕获异常，并在finally里countDown();
                // 如果没有捕获异常，子线程在执行过程中出现异常而导致没有countDown()，就会出现主线程阻塞
                count.countDown();
            }
        }
    }

    /**
     * <p>方式三</p>
     * 使用线程组 ThreadGroup
     */
    @Test
    public void test3() {
        int size = 3;
        ThreadGroup group = new ThreadGroup("group");

        System.out.println("主线程开始执行");
        for (int i = 0; i < size; i++) {
            int finalI = i;
            new Thread(group, "子线程" + i){
                @Override
                public void run() {
                    System.out.println("子线程 " + finalI + " 开始执行");
                    try {
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("子线程 " + finalI + " 执行线束");
                }
            }.start();

            // 给每个子线程执行增加时间点间隔
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 通过不停的查看线程组里是否有线程正在执行判断是否所有子线程已执行完成
        while (group.activeCount() != 0){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("主线程执行结束");
    }

    /**
     * <p>方式四</p>
     * 使用线程Thread.join()方法
     */
    @Test
    public void test4() throws InterruptedException {
        System.out.println("主线程开始执行");

        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            int finalI = i;
            Thread t = new Thread() {
                @Override
                public void run() {
                    System.out.println("子线程 " + finalI + " 开始执行");
                    try {
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("子线程 " + finalI + " 执行线束");
                }
            };
            t.start();
            list.add(t);
        }

        // 通过子线程的join()方法阻塞主线程，直到全部子线程执行结束
        for (Thread t: list) {
            t.join();
        }

        System.out.println("主线程执行结束");
    }
}
